﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.ServiceModel;
using System.ServiceProcess;
using System.Threading;
using System.Timers;
using SharpBits.Base;
using VMDS.Business.Entities;
using VMDS.Business.Processes;
using VMDS.Services.ClientService.MainServiceReference;
using VMDS.Services.Contracts;
using VMDS.Settings;
using Timer = System.Timers.Timer;

namespace VMDS.Services.ClientService
{
    using System.Security.Cryptography;
    using System.Text;

    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in both code and config file together.

    public class ClientService : IClientServiceContract
    {
        #region IClientServiceContract Members

        public void Register()
        {
            ClientServiceLogic.Subscribe();
        }

        public List<VM> GetOnlineVMs()
        {
            return ClientServiceLogic.GetOnlineVMs();
        }

        public List<VM> GetOfflineVMs()
        {
            return ClientServiceLogic.GetOfflineVMs();
        }

        public void Deploy(int id)
        {
            ClientServiceLogic.Deploy(id);
        }

        public List<TaskDataPack> GetCurrentOperations()
        {
            return ClientServiceLogic.TaskInfo;
        }

        public bool CheckUpdate()
        {
            return ClientServiceLogic.NeedUpdate();
        }

        public void Update()
        {
            ClientServiceLogic.UpdateProduct();
        }

        #endregion
    }

    public static class ClientServiceLogic
    {
        private static readonly List<IClientCallBack> subscribers = new List<IClientCallBack>();
        private static ClientConfiguration Cfg;
        private static Timer timer;
        private static BackgroundWorker updater;
        private static Cache cache;
        private static int MyId;
        private static List<Task> tasks;
        private static List<VM> vms;
        private static List<OS> oss;
        private static readonly List<DeployJob> Jobs = new List<DeployJob>();

        public static List<TaskDataPack> TaskInfo
        {
            get { return getTi(); }
        }

        private static MainServiceContractClient _client;
        public static MainServiceContractClient MS
        {
            get
            {
                if (_client == null) _client = new MainServiceContractClient();
                else
                {
                    if (_client.State == CommunicationState.Faulted)
                    {
                        try
                        {
                            _client.Close();
                            _client.Open();
                        }
                        catch (Exception)
                        {
                            _client = new MainServiceContractClient();
                        }
                    }
                }
                return _client;
            }
        }

        private static List<TaskDataPack> getTi()
        {
            var result = new List<TaskDataPack>();
            foreach (DeployJob job in Jobs)
            {
                if (job.IsCompleted) continue;
                
                var ti = new TaskDataPack
                             {
                                 CurrentOperation = job.Message,
                                 CurrentProgress = job.Progress,
                                 ID = job.Id,
                                 VmID = job.VmOrOsId,
                                 VmName = job.VmOrOsName
                             };
                result.Add(ti);
            }
            return result;
        }

        private static void AddTaskInfo(TaskDataPack t)
        {
            foreach (IClientCallBack callBack in subscribers.ToArray())
            {
                try
                {
                    if (((ICommunicationObject) callBack).State == CommunicationState.Opened)
                    {
                        callBack.OnTaskAdded(t);
                    }
                    else
                    {
                        subscribers.Remove(callBack);
                    }
                }
                catch
                {
                }
            }
        }

        public static void CompleteOrDeleteTaskInfo(TaskDataPack t)
        {
            foreach (IClientCallBack callBack in subscribers.ToArray())
            {
                try
                {
                    if (((ICommunicationObject) callBack).State == CommunicationState.Opened)
                    {
                        callBack.OnTaskCmpleted(t);
                    }
                    else
                    {
                        subscribers.Remove(callBack);
                    }
                }
                catch
                {
                }
            }
        }

        public static void ChangeTaskInfo(TaskDataPack t)
        {
            foreach (IClientCallBack callBack in subscribers.ToArray())
            {
                try
                {
                    if (((ICommunicationObject) callBack).State == CommunicationState.Opened)
                    {
                        callBack.OnTaskChanged(t);
                    }
                    else
                    {
                        subscribers.Remove(callBack);
                    }
                }
                catch
                {
                }
            }
        }

        public static bool Subscribe()
        {
            try
            {
                var callback = OperationContext.Current.GetCallbackChannel<IClientCallBack>();
                if (!subscribers.Contains(callback))
                    subscribers.Add(callback);
                return true;
            }
            catch
            {
                return false;
            }
        }

        public static bool Unsubscribe()
        {
            try
            {
                var callback = OperationContext.Current.GetCallbackChannel<IClientCallBack>();
                if (subscribers.Contains(callback))
                    subscribers.Remove(callback);
                return true;
            }
            catch
            {
                return false;
            }
        }
        static void CheckVmwareAuthdService()
        {
            ServiceController sc = new ServiceController("VMAuthdService");
            if (sc.Status!=ServiceControllerStatus.Running) sc.Start();
        }
        public static bool NeedUpdate()
        {
            try { CheckVmwareAuthdService();}
            catch { }

            try
            {
                SetPermissions();
                string Destination = MS.GetUpdateUNC();
                string Setup = Destination + "\\setup.exe";
                string msi = Destination + "\\wixsetup.msi";
                if (!File.Exists(Setup))
                {
                    return false;
                }
                if (!File.Exists(msi))
                {
                    return false;
                }
                var soft = new SoftWare();
                soft.Productname = "VMDS";
                soft.Update();
                if (String.IsNullOrWhiteSpace(soft.ProductVersion)) return false;
                string updVer = MSI.GetVersionInfo(msi);
                if (soft.ProductVersion == updVer) return false;
                return true;
            }
            catch
            {
                return false;
            }
        }

    

        public static void UpdateProduct()
        {
            string Destination = MS.GetUpdateUNC();
            string Setup = Destination + "\\setup.exe";
            string msi = Destination + "\\wixsetup.msi";
            if (!File.Exists(Setup))
            {
                return;
            }
            if (!File.Exists(msi))
            {
                return;
            }
            var soft = new SoftWare();
            soft.Productname = "VMDS";
            soft.Update();
            if (String.IsNullOrWhiteSpace(soft.ProductVersion)) return;
            string updVer = MSI.GetVersionInfo(msi);
            if (soft.ProductVersion == updVer) return;

            Logger.WriteLog("try to update product");
            try
            {
                Process.Start(Destination + "\\setup.exe", "x");
            }
            catch
            {
                FindAndKillProcess("vmds");
            }
        }

        public static bool FindAndKillProcess(string name)
        {
            var p = Process.GetProcesses();
            foreach (Process clsProcess in p)
            {
                if (clsProcess.ProcessName.ToLower() == name.ToLower())
                {
                    clsProcess.Kill();
                    return true;
                }
            }
            return false;
        } 


        static void SetPermissions()
        {
            var path = "d:\\virtual";
            var arg = path + " /t /e /g Все:f";
            ProcessStartInfo pi = new ProcessStartInfo("cacls", arg);
            pi.WindowStyle = ProcessWindowStyle.Hidden;
            pi.CreateNoWindow = true;
            
            Process.Start(pi);
        }

        public static void Test()
        {
            SetPermissions();
            Cfg = new ClientConfiguration("d:\\virtual\\data\\config.ini");
            cache = new Cache(Cfg);
            double interval = (new Random().Next(0, 30) + 30)*1000;
            Logger.WriteLog("Update interval is " + interval);
            timer = new Timer(interval);
            timer.Elapsed += timer_Elapsed;
            timer.Enabled = true;
            updater = new BackgroundWorker();
            updater.DoWork += updater_DoWork;
            try
            {
                InitJobs();
            }
            catch 
            {
                
            }

           
        }

        private static DeployJob GetJobById(int Id, bool IsOs)
        {
            return Jobs.FirstOrDefault(x => (x.VmOrOsId == Id) && (x.IsOS == IsOs));
        }

        private static string share;

        private static bool startcheck = false;

        private static void InitJobs()
        {
            tasks = MS.GetMyTasks(Environment.MachineName);
            oss = MS.GetOSs();
            if (!startcheck)
            {
                CheckCrc(oss);
            }

            startcheck = true;
            vms = MS.GetVMs();
            share = MS.GetShareUNC();
            var list = new List<DeployJob>();
            list.AddRange(Jobs);
            foreach (var deployJob in list)
            {
                if (deployJob.IsCompleted)
                {
                    var j = Jobs.SingleOrDefault(x => x.Id == deployJob.Id);
                    Jobs.Remove(j);
                }
            }
          
            foreach (Task task in tasks)
            {
                DeployJob job = GetJobById(task.VMId, false);
                if (job == null)
                {
                    VM vm = vms.SingleOrDefault(x => x.Id == task.VMId);
                    if (vm == null) continue;
                    try
                    {
                        job = new DeployJob(vm, task, share, Cfg.LocalStorage, false);
                    }
                    catch 
                    {
                       continue;
                    } 
                    job.Changed += job_Changed;
                    job.Completed += job_Completed;
                    Jobs.Add(job);
                    var ti = new TaskDataPack
                                 {
                                     CurrentOperation = job.Message,
                                     CurrentProgress = job.Progress,
                                     ID = job.Id,
                                     VmID = job.VmOrOsId,
                                     VmName = job.VmOrOsName
                                 };
                    AddTaskInfo(ti);
                }
                else job.UpdatePriority();
            }
            if (!IsIdentical(oss, cache.InstalledOSs))
            {
                foreach (OS os in oss)
                {
                    DeployJob job = GetJobById(os.Id, true);
                    if (job == null)
                    {
                        job = new DeployJob(os, share, Cfg.LocalStorage);
                        job.Completed += job_Completed;
                        job.Changed += job_Changed;
                        Jobs.Add(job);
                        var ti = new TaskDataPack
                                     {
                                         CurrentOperation = job.Message,
                                         CurrentProgress = job.Progress,
                                         ID = job.Id,
                                         VmID = job.VmOrOsId,
                                         VmName = job.VmOrOsName
                                     };
                        AddTaskInfo(ti);
                    }
                }
            }
           
        }
        static void ClearBits()
        {
            var man = new BitsManager();
            BitsJobs jobs = man.EnumJobs();
            var todel = jobs.Where(x => (x.Value.DisplayName.Contains("VMDS_")) && (x.Value.State != JobState.Transferring));
            foreach (var pair in todel)
            {
                pair.Value.Cancel();
            }

        }
        private static void job_Completed(object sender, EventArgs e)
        {
            var job = (DeployJob) sender;
            var ti = new TaskDataPack
                         {
                             CurrentOperation = job.Message,
                             CurrentProgress = job.Progress,
                             ID = job.Id,
                             VmID = job.VmOrOsId,
                             VmName = job.VmOrOsName
                         };
            if (!job.IsOS)
            {
                List<VM> inst = cache.InstalledVMs;
                VM vm = vms.SingleOrDefault(x => x.Id == job.VmOrOsId);
                if (!inst.Any(x=>x.Id==vm.Id)) inst.Add(vm);
                cache.InstalledVMs = inst;
                int id = job.Task.Id;
                if (job.IsManual)
                {
                    Task t =
                        MS.GetMyTasks(Environment.MachineName).FirstOrDefault(
                            x => (x.VMId == job.VmOrOsId) && (x.IsManual));
                    id = t.Id;
                }
                MS.CompleteTask(id);
               

            }
            else
            {
                List<OS> installed = cache.InstalledOSs;
                OS os = oss.SingleOrDefault(x => x.Id == job.VmOrOsId);
                if (!installed.Any(x => x.Id == job.VmOrOsId)) installed.Add(os);
                cache.InstalledOSs = installed;
            }
            CompleteOrDeleteTaskInfo(ti);
            Jobs.Remove(job);
            SetPermissions();
        }

        private static void job_Changed(object sender, EventArgs e)
        {
            var job = (DeployJob) sender;
            var ti = new TaskDataPack
                         {
                             CurrentOperation = job.Message,
                             CurrentProgress = job.Progress,
                             ID = job.Id,
                             VmID = job.VmOrOsId,
                             VmName = job.VmOrOsName
                         };
            ChangeTaskInfo(ti);
        }

        private static bool IsIdentical(List<OS> ossOnline, List<OS> ossLocal)
        {
            if (ossOnline.Count != ossLocal.Count) return false;
            foreach (OS v in ossOnline)
            {
                OS x = ossLocal.SingleOrDefault(c => c.Id == v.Id);
                if (x == null) return false;
                if (x.Version != v.Version) return false;
            }
           
            return true;
        }

        static void ReinstallOS(string name)
        {
            var collection = cache.InstalledOSs;
            var selected = collection.SingleOrDefault(x => x.Name == name);
            if (selected == null)
            {
                return;
            }

            collection.Remove(selected);
            cache.InstalledOSs = collection;
        }

        private static void CheckCrc(List<OS> ossOnline)
        {
           try
           {
               foreach (var online in ossOnline)
               {
                   var dir = share + "\\OS\\" + online.Name;
                   var path = dir + "\\crc.xml";
                   if (File.Exists(path))
                   {
                       var files = Directory.EnumerateFiles(Cfg.LocalStorage + "OS\\" + online.Name);
                       var crcs = new List<FileCrc>();
                       var originalCrc = new List<FileCrc>();
                       originalCrc = XmlTool.Load(path, originalCrc.GetType());
                       foreach (var file in files)
                       {
                           var st = new Stopwatch();
                           st.Start();
                           var crc = Hasher.GetMD5HashFromFile(file);
                           st.Stop();
                           var sname = new FileInfo(file).Name;
                           crcs.Add(new FileCrc() { MD5 = crc, Name = sname });
                       }

                       foreach (var crc in originalCrc)
                       {
                           var present = crcs.SingleOrDefault(x => x.Name == crc.Name);
                           if ((present == null) || (present.MD5 != crc.MD5))
                           {
                               ReinstallOS(online.Name);
                           }
                       }
                   }
               }
           }
           catch
           {

           }
       }

        private static void updater_DoWork(object sender, DoWorkEventArgs e)
        {
            if (!MsAvailable())
            {
                Logger.WriteLog("Main Service is unavailable...", true);
                return;
            }
            try
            {
                ClientPC me = MS.GetPCs().FirstOrDefault(x => x.Name == Environment.MachineName);
                if (me != null) MyId = me.Id;
                InitJobs();
               
            }
            catch (Exception exception)
            {
                Logger.WriteLog(exception.ToString(), true);
            }
        }

        private static void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            try
            {
                if (!updater.IsBusy) updater.RunWorkerAsync();
            }
            catch (Exception ex)
            {
                Logger.WriteLog(ex.Message, true);
            }
        }

        #region Implementation of IClientService

        public static List<VM> GetOnlineVMs()
        {
            Logger.WriteLog("VMs request...");
            if (MsAvailable())
            {
                try
                {
                    Logger.WriteLog("Getting from MS");
                    cache.OnlineVMs = MS.GetVMs();
                }
                catch (Exception)
                {
                    Logger.WriteLog("Getting from cache");
                    throw;
                }
            }
            return cache.OnlineVMs;
        }

        private static bool MsAvailable()
        {
            bool result = false;
            try
            {
                MS.GetShareUNC();
                result = true;
            }
            catch (Exception e)
            {
                Logger.WriteLog(e.Message, true);
                result = false;
            }
            return result;
        }

        public static List<VM> GetOfflineVMs()
        {
            return cache.InstalledVMs;
        }

        private static void _deploy(object _id)
        {
            var id = (int) _id;
            ClientPC me = MS.GetPCs().FirstOrDefault(x => x.Name == Environment.MachineName);
            DeployJob job = GetJobById(id, false);
            if (me != null) MyId = me.Id;
            if (job == null)
            {
                var task = new Task
                               {
                                   ClientPCId = MyId,
                                   CreatedBy = Environment.UserName,
                                   Priority = TaskPriorities.Medium,
                                   IsManual = true,
                                   VMId = id
                               };
                MS.AddTasks(new List<Task> {task});
                InitJobs();
            }
        }

        public static void Deploy(int id)
        {
            var t = new Thread(_deploy);
            t.Start(id);
        }

        #endregion
    }
}